package com.lagou.context;

import com.lagou.aop.ETransactional;
import com.lagou.factory.ProxyFactory;
import com.lagou.ioc.EAutowired;

import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class EApplicationContext {
    //包名
    private String packageName;

    //单例池
    private final Map<String, Object> singleObjects = new ConcurrentHashMap<>();

    //早期单例池
    private final Map<String, Object> earlySingleObjects = new ConcurrentHashMap<>();

    //类型工厂 需要注入时，取出工厂进行初始化 解决循环依赖
    private final Map<String, BeanFactory<?>> singleFactories = new ConcurrentHashMap<>();


    public EApplicationContext() {
    }

    public EApplicationContext(String packageName) {
        this.packageName = packageName;
        initBeans();
    }

    private void initBeans() {
        List<Class> classList = loadClasses(packageName);
        flushBeans(classList);
    }

    private List<Class> loadClasses(String packageName) {
        List<Class> classList = new ArrayList<>();
        String packageDirName = packageName.replace(".", "/");
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if (protocol.equals("file")) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByType(packageName, filePath, classList);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }

    private void findAndAddClassesInPackageByType(String packageName, String filePath, List<Class> classList) {
        File dir = new File(filePath);
        //选出文件夹下面所有的文件
        File[] files = dir.listFiles(new FileFilter() {
            public boolean accept(File file) {
                return (file.isDirectory() || file.getName().endsWith(".class"));
            }
        });
        for (File file : files) {
            if (file.isDirectory()) {
                //递归一下
                findAndAddClassesInPackageByType(packageName + "." + file.getName(), file.getAbsolutePath(), classList);
            } else {
                String className = file.getName().substring(0, file.getName().length() - ".class".length());
                if (className.contains("$"))
                    className = file.getName().substring(0, className.indexOf("$"));
                try {
                    Class cla = Class.forName(packageName + "." + className);
                    if (cla.equals(this.getClass().getName()))
                        continue;
                    //加入工厂池，解决循环依赖
                    this.singleFactories.put(cla.getName(), new BeanFactory<>(cla));
                    classList.add(cla);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void flushBeans(List<Class> classList) {
        try{
            for (Class aClass : classList) {
                doCreateBean(aClass, classList);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void doCreateBean(Class aClass, List<Class> classList) {
        try{

            if (aClass.isInterface()){
                return;
            }
            if(this.singleObjects.containsKey(aClass.getName()))
                return;
            Object obj = getBeanCache(aClass);

            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for(Field field : declaredFields){
                if (!field.isAnnotationPresent(EAutowired.class))
                    continue;
                Class fieldType = field.getType();
                Object fieldValue = null;
                if (fieldType.isInterface()){
                    for(Class implClass : classList) {
                        if (implClass.isInterface()) {
                            continue;
                        }
                        if (fieldType.isAssignableFrom(implClass)) {
                            Constructor[] constructors = implClass.getConstructors();
                            for(Constructor constructor : constructors){
                                int parameterCount = constructor.getParameterCount();
                                if(parameterCount==0){
                                    fieldValue = getSingleton(implClass.getName());
                                }
                            }
                            break;
                        }
                    }
                }else {
                    fieldValue = getSingleton(fieldType.getName());
                }

                field.setAccessible(true);
                field.set(obj, fieldValue);
            }

            this.earlySingleObjects.remove(aClass.getName());
            this.singleObjects.put(aClass.getName(), obj);

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    protected Object getSingleton(String beanName) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        Object singletonObject = this.singleObjects.get(beanName);
        if (singletonObject == null ) {
            singletonObject = this.earlySingleObjects.get(beanName);
            if (singletonObject == null) {
                synchronized (this.singleObjects) {
                    singletonObject = this.singleObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingleObjects.get(beanName);
                        if (singletonObject == null) {
                            BeanFactory<?> singletonFactory = this.singleFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingleObjects.put(beanName, singletonObject);
                                this.singleFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

    public Object getBeanCache(Class aClass) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        Object obj = null;
        if (this.earlySingleObjects.containsKey(aClass.getName())){
            obj = this.earlySingleObjects.get(aClass.getName());
        }else {
            obj = this.singleFactories.get(aClass.getName()).getObject();
            this.singleFactories.remove(aClass.getName());
            this.earlySingleObjects.put(aClass.getName(), obj);
        }
        return obj;
    }

    public Object getBean(String beanName, EApplicationContext customApplicationContext) {
        Object beanObject = this.singleObjects.get(beanName);
//        try{
//            referenceBindObject(beanObject);
//        }catch (Exception e){
//            e.printStackTrace();
//        }

        Class aClass = beanObject.getClass();
        Annotation annotation = aClass.getAnnotation(ETransactional.class);
        if (annotation != null) {
            ProxyFactory proxyFactory = (ProxyFactory) customApplicationContext.getBean(ProxyFactory.class.getName(), customApplicationContext);
            beanObject = proxyFactory.getJdkProxy(beanObject);
        }
        return beanObject;
    }

}
